package cn.stimd.spring.context.event;

import cn.stimd.spring.context.ApplicationContext;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class ApplicationListenerMethodAdapter implements GenericApplicationListener {
    private final String beanName;
    private final Method method;
    private final Class<?> targetClass;
    private final Method bridgedMethod;
    private final List<ResolvableType> declaredEventTypes;
    private ApplicationContext applicationContext;


    public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method, ApplicationContext applicationContext) {
        this.beanName = beanName;
        this.method = method;
        this.targetClass = targetClass;
        this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
        this.applicationContext = applicationContext;

        Method targetMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
        EventListener ann = AnnotatedElementUtils.findMergedAnnotation(targetMethod, EventListener.class);
        this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
    }


    //解析方法参数的类型，即事件类型
    private List<ResolvableType> resolveDeclaredEventTypes(Method method, EventListener ann) {
        int count = method.getParameterTypes().length;
        if (count > 1) {
            throw new IllegalStateException("EventListener方法只能有一个参数，方法名: " + method);
        }
        if (ann != null && ann.value().length > 0) {
            List<ResolvableType> types = new ArrayList<>(ann.value().length);
            for (Class<?> eventType : ann.value()) {
                types.add(ResolvableType.forClass(eventType));
            }
            return types;
        }
        else {
            if (count == 0) {
                throw new IllegalStateException("Event parameter is mandatory for event listener method: " + method);
            }
            return Collections.singletonList(ResolvableType.forMethodParameter(method, 0));
        }
    }


    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        Object[] args = resolveArguments(event);
        doInvoke(args);
    }


    protected Object[] resolveArguments(ApplicationEvent event) {
        //如果@EventListener注解声明的事件列表与事件类型不匹配，则返回空
        ResolvableType declaredEventType = getResolvableType(event);
        if (declaredEventType == null) {
            return null;
        }

        //方法没有参数，返回空数组
        if (this.method.getParameterTypes().length == 0) {
            return new Object[0];
        }
        //将事件对象作为方法的参数
        return new Object[] {event};
    }


    protected Object doInvoke(Object... args) {
        Object bean = this.applicationContext.getBean(this.beanName);
        ReflectionUtils.makeAccessible(this.bridgedMethod);
        try {
            return this.bridgedMethod.invoke(bean, args);
        }
        catch (Exception ex) {
            throw new IllegalStateException("调用EventListener方法失败", ex);
        }
    }


    private ResolvableType getResolvableType(ApplicationEvent event) {
        for (ResolvableType declaredEventType : this.declaredEventTypes) {
            if (declaredEventType.getRawClass().isInstance(event)) {
                return declaredEventType;
            }
        }
        return null;
    }


    @Override
    public boolean supportsEventType(ResolvableType eventType) {
        for (ResolvableType declaredEventType : this.declaredEventTypes) {
            if (declaredEventType.isAssignableFrom(eventType)) {
                return true;
            }
        }
        return eventType.hasUnresolvableGenerics();
    }


    //默认支持所有事件源
    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return true;
    }
}
